﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using MRSAPI.Managed;
using DIPCommon = MSAS.DIP.Common;
using Microsoft.Reputation.Tests.FrontEndCommon;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.Reputation.Tests.FrontEndCommon
{
    public class MrsApiHelper : IDisposable
    {
        public const uint S_OK = 0;
        public const uint S_FALSE = 1;

        public UIntPtr m_pCache = UIntPtr.Zero;        // pointer to the Test Cache.
        public UIntPtr m_pCanonicalUrl = UIntPtr.Zero; // pointer to the CanonicalUrl object which needs to be passed to Categorize().
        public int numberOfVariants = 0;
        public string inputUrl = string.Empty;

        public int REQUEST_DATA_FLAG = 0;               // 0 means REQUESTDATA_FLAG_DEFAULT
        public string dataBlockBase64 = string.Empty;   // string to store the DataBlock generated from the GetRequest* functions.
        public byte[] dataBlockByte = null;
        public bool funcResult = true;                  // variable which stores the WINAPI result of functions which return one. This variable can be checked to assess if the function call passed.
        public IntPtr urlSize = IntPtr.Zero;
        public UInt64 clientVersion = 0;
        public IntPtr origNumVariants = IntPtr.Zero;
        public IntPtr variantsInDataBlock = IntPtr.Zero;
        public IntPtr sizeOfHash = IntPtr.Zero;
        public string requestUrl = string.Empty;

        public UIntPtr ppVariant = UIntPtr.Zero;        // This object will store information for MsasApi::IRefUrlVariant ** ppVariant.
        public string variant_url = string.Empty;
        public byte[] variant_sha = null;
        public string variant_canonUrl = string.Empty;
        public byte[] variant_canonSha = null;

        public UIntPtr ppUrlVariants = UIntPtr.Zero; // This object will store information for MsasApi::IRefUrlVariantVector ** ppUrlVariants.

        public MrsApiHelper()
        {
        }

        public MrsApiHelper(string url)
        {
            uint result = MrsApiTestInterop.MsasCreateCanonicalUrlW(
                url,
                0,
                out m_pCanonicalUrl);

            if (result != S_OK)
            {
                funcResult = false;
                Console.WriteLine("Creation of CanonicalURL FAILED!!");
            }

            using (DIPCommon.MsasApi msasApi = new DIPCommon.MsasApi(url))
            {
                numberOfVariants = msasApi.VariantCount;
            }

            inputUrl = url;
        }

        ~MrsApiHelper()
        {
            Dispose(false);
        }

        public bool ImportUrfFileToCache(string urfFile, byte bloomBits)
        {
            if (m_pCache != UIntPtr.Zero)
            {
                Console.WriteLine("The Cache Object is not Empty! This should not be the case, please check your code. Setting it to UIntPtr.Zero.");
            }
            
            uint result = MrsApiTestInterop.MrsCreateTestCacheW(
                urfFile,
                0, //MSASAPI_TESTCACHE_FLAGS.MSASAPI_TESTCACHE_NONE,
                bloomBits,
                out m_pCache);

            if (result != S_OK)
            {
                Console.WriteLine("MrsApiTestInterop.CreateTestCache() failed with ERROR:{0}", result);
                return false;
            }

            return true;
        }

        public static List<byte[]> GetVariantsForUrl(string url)
        {
            return VariantGenerator.GetVariantHashes(url, 0); // This could be more efficient by calling this it in the constructor itself, as we are already making the MsasApi object.
        }

        public static string GetCanonicalizedUrl(string url)
        {   
            StringBuilder canonUrl = new StringBuilder(100, 2100);
            IntPtr size = IntPtr.Zero; 
            byte[] hash = new byte[32];
            uint result = MrsApiTestInterop.MsasGetCanonicalUrlW(
                url,
                0,
                canonUrl,
                (IntPtr)2100,
                out size,
                hash,
                (IntPtr)32);

            if (result != S_OK)
            {
                Console.WriteLine("MrsApiTestInterop.MsasGetCanonicalUrlW() failed with ERROR:{0}", result);
                return "false";
            }

            return canonUrl.ToString();
        }

        public static byte[] GetShaForUrl(string url)
        {
            using (DIPCommon.MsasApi msasApi = new DIPCommon.MsasApi(url))
            {
                return msasApi.GetVariant(0).sha256;
            }
        }

        // Commenting this out as no one uses this.

        //public bool GetDataBlockBase64()
        //{
        //    IntPtr actualSize = IntPtr.Zero;
            
        //    // Making the first dummy call to get the size of the DataBlock.
        //    uint result = MrsApiTestInterop.MsasGetRequestString(
        //       m_pCanonicalUrl,
        //       null,
        //       IntPtr.Zero,
        //       out actualSize);

        //    Console.WriteLine("Datablock call succeeded, required size is {0}", actualSize.ToInt32());

        //    if (result != S_OK)
        //    {
        //        Console.WriteLine("MrsApiTestInterop.GetDataBlockBase64() failed with ERROR:{0}", result);
        //        return false;
        //    }

        //    StringBuilder block = new StringBuilder(actualSize.ToInt32());
        //    actualSize = IntPtr.Zero;

        //    // Making the actual call by passing in the correct size and a StringBuilder with the needed Capacity.
        //    result = MrsApiTestInterop.MsasGetRequestString(
        //       m_pCanonicalUrl,
        //       block,
        //       (IntPtr)block.Capacity,
        //       out actualSize);

        //    if (result != S_OK)
        //    {
        //        Console.WriteLine("MrsApiTestInterop.MsasGetRequestString() failed with ERROR:{0}", result);
        //        return false;
        //    }

        //    dataBlockBase64 = block.ToString();
        //    return true;
        //}

        public bool GetDataBlockByteArray()
        {
            IntPtr actualSize = IntPtr.Zero;

            // Making the first dummy call to get the size of the DataBlock.
            uint result = MrsApiTestInterop.MsasGetRequestData(
               m_pCanonicalUrl,
               REQUEST_DATA_FLAG,
               null,
               IntPtr.Zero,
               out actualSize);
            //Console.WriteLine("Datablock call succeeded, required size is {0}", actualSize.ToInt32());

            if (result != S_OK)
            {
                Console.WriteLine("MrsApiTestInterop.GetDataBlockByteArray() failed with ERROR:{0}", result);
                //return false; // This is commented out for now as there is a bug in which the GetRequestData functions returns ERROR_INSUFFICIENT_BUFFER instead of S_OK.
            }

            byte[] block = new byte[actualSize.ToInt32()];
            actualSize = IntPtr.Zero;

            // Making the actual call by passing in the correct size and a StringBuilder with the needed Capacity.
            result = MrsApiTestInterop.MsasGetRequestData(
               m_pCanonicalUrl,
               REQUEST_DATA_FLAG,
               block,
               (IntPtr)block.Length,
               out actualSize);

            if (result != S_OK)
            {
                Console.WriteLine("MrsApiTestInterop.MsasGetRequestData() failed with ERROR:{0}", result);
                return false;
            }

            dataBlockByte = new byte[block.Length];
            dataBlockByte = block;
            return true;
        }

        public bool ExtractRequestData(byte[] dataBlock, int dbSize, out uint result)
        {
            //StringBuilder urlBuffer = new StringBuilder(100, 5000);
            StringBuilder urlBuffer = new StringBuilder(5000);

            result = MrsApiTestInterop.MsasExtractRequestDataW(
                dataBlock,
                (IntPtr)dbSize,
                urlBuffer,
                (IntPtr)5000,
                out urlSize,
                out clientVersion,
                out origNumVariants,
                out variantsInDataBlock,
                out sizeOfHash);

            if (result != S_OK)
            {
                Console.WriteLine("ExtractRequestData() FAILED with Error:{0}", result);
                return false;
            }

            requestUrl = urlBuffer.ToString();
            urlBuffer = null;

            return true;
        }

        public uint MrsTestGetCanonicalVariant(uint variantFlags)
        {
            uint result = MrsApiTestInterop.MrsTestGetCanonicalVariant(
                m_pCanonicalUrl,
                variantFlags,
                out ppVariant);

            if (result != S_OK)
            {
                Console.WriteLine("MrsTestGetCanonicalVariant() FAILED with Error:{0}", result);
                return result;
            }

            return result;
        }

        public uint MrsTestGetCanonicalVariantUrlW(uint variantFlags, bool includeScheme)
        {
            UIntPtr requiredSize = UIntPtr.Zero;

            // Making the first dummy call to get the size required for the Canonical URL Buffer.
            uint result = MrsApiTestInterop.MrsTestGetCanonicalVariantUrlW(
                m_pCanonicalUrl,
                variantFlags,
                includeScheme,
                null,
                UIntPtr.Zero,
                out requiredSize,
                null,
                UIntPtr.Zero);

            if (result != S_OK)
            {
                Console.WriteLine("First call to MrsTestGetCanonicalVariantUrlW() FAILED with Error:{0}", result);
                return result;
            }

            //Console.WriteLine("Required size returned was {0}", requiredSize.ToUInt32());
            StringBuilder pszCanonicalUrl = new StringBuilder((int)requiredSize.ToUInt32());
            byte[] sha = new byte[32];

            // Now making the actual call.
            result = MrsApiTestInterop.MrsTestGetCanonicalVariantUrlW(
                m_pCanonicalUrl,
                variantFlags,
                includeScheme,
                pszCanonicalUrl,
                (UIntPtr)requiredSize.ToUInt32(),
                out requiredSize,
                sha,
                (UIntPtr)sha.Length);

            if (result != S_OK)
            {
                Console.WriteLine("Second call to MrsTestGetCanonicalVariantUrlW() FAILED with Error:{0}", result);
                return result;
            }

            variant_canonUrl = pszCanonicalUrl.ToString();
            variant_canonSha = new byte[sha.Length];
            variant_canonSha = sha;

            return result;
        }

        /// <summary>
        /// This functions will extract out the particular Variants from a MsasApi::IRefUrlVariantVector Variant Vector. 
        /// It will return a MsasApi::IRefUrlVariant.
        /// </summary>
        /// <param name="index"></param>
        /// <returns>HRESULT</returns>
        public uint MrsTestGetVariant(int index)
        {
            uint result = MrsApiTestInterop.MrsTestGetVariant(
                ppUrlVariants,
                (UIntPtr)index,
                out ppVariant);

            if (result != S_OK)
            {
                Console.WriteLine("First call to MrsTestGetVariantUrlW() FAILED with Error:{0}", result);
            }

            return result;
        }

        /// <summary>
        /// This function takes in a variant of type MsasApi::IRefUrlVariant.
        /// </summary>
        /// <param name="includeScheme"></param>
        /// <returns></returns>
        public uint MrsTestGetVariantUrlW(bool includeScheme)
        {
            UIntPtr requiredSize = UIntPtr.Zero;

            // Making the first dummy call to get the size required for the Canonical URL Buffer.
            uint result = MrsApiTestInterop.MrsTestGetVariantUrlW(
                ppVariant,
                includeScheme,
                null,
                UIntPtr.Zero,
                out requiredSize,
                null,
                UIntPtr.Zero);

            if (result != S_OK)
            {
                Console.WriteLine("First call to MrsTestGetVariantUrlW() FAILED with Error:{0}", result);
                return result;
            }

            //Console.WriteLine("Required size returned was {0}", requiredSize.ToUInt32());
            StringBuilder pszCanonicalUrl = new StringBuilder((int)requiredSize.ToUInt32());
            byte[] sha = new byte[32];

            // Now making the actual call.
            result = MrsApiTestInterop.MrsTestGetVariantUrlW(
                ppVariant,
                includeScheme,
                pszCanonicalUrl,
                (UIntPtr)requiredSize.ToUInt32(),
                out requiredSize,
                sha,
                (UIntPtr)sha.Length);

            if (result != S_OK)
            {
                Console.WriteLine("Second call to MrsTestGetVariantUrlW() FAILED with Error:{0}", result);
                return result;
            }


            variant_url = pszCanonicalUrl.ToString();
            variant_sha = new byte[sha.Length];
            variant_sha = sha;

            return result;
        }

        /// <summary>
        /// This function takes in the URL and returns the MsasApi::IRefUrlVariantVector. Using the Vector we can then get a particular variant and do operations/validations on it.
        /// </summary>
        /// <param name="url">The input raw URL.</param>
        /// <returns></returns>
        public uint MsasCreateUrlVariantsW(string url)
        {
            uint result = DIPCommon.MsasApi.MsasCreateUrlVariantsW(
                url,
                0,  // MSAS_CANONICAL_FLAGS
                out ppUrlVariants);

            if (result != S_OK)
            {
                Console.WriteLine("MsasCreateUrlVariantsW() FAILED with Error:{0}", result);
            }

            return result;
        }

        public bool Categorize(int[] categoryIDs, out int[] categoryStats, int length)
        {
            // Have to make a new int[] of same length to pass to the Interop function. If this is not done, .Net complains that categoryStats, which is an our param, is never assigned a value.
            int[] resultConf = new int[length];

            uint result = MrsApiTestInterop.MrsCategorizeUrl(
               m_pCanonicalUrl,
               m_pCache,
               categoryIDs,
               resultConf,
               length,
               0,
               false);

            categoryStats = resultConf;

            if (result != S_OK)
            {
                Console.WriteLine("Categorize() FAILED with Error:{0}", result);
                return false;
            }

            return true;
        }

        #region CommonCode

        /// <summary>
        /// 
        /// </summary>
        /// <returns>path to the URF File</returns>
        public static string ConvertLookupResponseToUrfFile(IList<MrsLookupResponse> results, string filePath)
        {
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
            return UrfFileParser.ConvertLookupResponseToURF(results, filePath);
        }

        public static void ImportUrfFileToDB(string filePath)
        {
            MrsDeliveryServiceHelper.InjectTestDataToDB(filePath);
        }

        public static void ClearTestDataFromDB()
        {
            TestDataInjector dataInj = new TestDataInjector();
            dataInj.ClearTestData();
        }

        public static bool SubArrayExistsFirst<T>(T[] array, T[] subArray, out int pos) // This will return the First time it finds the SubArray.
        {
            T[] a1 = new T[1];
            T[] s1 = new T[1];
            int tempPos = 0;
            bool exist = true;
            for (int i = 0; i < (array.Length - subArray.Length + 1); i++)
            {
                if (array[i].Equals(subArray[0]))
                {
                    tempPos = 0;
                    exist = true;
                    for (int j = 1; j < subArray.Length; j++) // Since the first element matched, checking for the remaining elements using recursion.
                    {
                        a1[0] = array[i+j];
                        s1[0] = subArray[j];
                        if(!SubArrayExistsFirst<T>(a1, s1, out tempPos))
                        {
                            exist = false;
                            break;
                        }
                    }
                    if (exist)
                    {
                        pos = i;
                        return true;
                    }
                }
            }
            pos = 0;
            return false;
        }

        public static byte[] GetShaByteArray(int count)
        {
            byte[] defaultSha = {218,220,54,133,103,13,137,170,153,225,202,197,162,161,50,99,40,98,42,22,46,251,102,55,33,101,145,143,40,223,60,45};
            byte[] result = new byte[count*defaultSha.Length];
            int index = 0;
            for (int i = 0; i < count; i++)
            {
                Array.Copy(defaultSha, 0, result, index, defaultSha.Length);
                index += defaultSha.Length;
            }

            return result;
        }

        #endregion

        #region Destroy Functions
        /// <summary>
        /// This function just calls the individial destroy fnctions for the Cache and CanonicalURL objects.
        /// Although we have the Dispose function which does the same thing, its important to call these functions from the test case so that we dont leak memory.
        /// When running the tests in Stress or Loop mode, Dispose might not be called until the tests are over. Calling these each time ensures no memory is leaked.
        /// </summary>
        public void Destroy()
        {
            DestoryTestCache();
            DestroyCanonicalUrl();
        }

        public void DestoryTestCache()
        {
            MrsApiTestInterop.MrsDestroyTestCache(m_pCache);
            m_pCache = UIntPtr.Zero;
        }

        public void DestroyCanonicalUrl()
        {
            MrsApiTestInterop.MsasDestroyCanonicalUrl(m_pCanonicalUrl);
            m_pCanonicalUrl = UIntPtr.Zero;
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            //Console.WriteLine("DISPOSING the MRSAPIHELPER Object.");
            if (m_pCache != UIntPtr.Zero)
            {
                //Console.WriteLine("Disposing m_pCache");
                MrsApiTestInterop.MrsDestroyTestCache(m_pCache);
                m_pCache = UIntPtr.Zero;
            }
            if (m_pCanonicalUrl != UIntPtr.Zero)
            {
                //Console.WriteLine("Disposing m_pCanonicalUrl");
                MrsApiTestInterop.MsasDestroyCanonicalUrl(m_pCanonicalUrl);
                m_pCanonicalUrl = UIntPtr.Zero;
            }

            if (dataBlockByte != null)
            {
                dataBlockByte = null;
            }

            if (urlSize != IntPtr.Zero)
            {
                urlSize = IntPtr.Zero;
            }

            if (origNumVariants != IntPtr.Zero)
            {
                origNumVariants = IntPtr.Zero;
            }

            if (variantsInDataBlock != IntPtr.Zero)
            {
                variantsInDataBlock = IntPtr.Zero;
            }

            if (sizeOfHash != IntPtr.Zero)
            {
                sizeOfHash = IntPtr.Zero;
            }

        }
        #endregion
    }
}
